/*
 * cmd_line.h
 *
 *  Created on: 2011-4-7
 *      Author: kenping.liu
 */

#ifndef CMD_LINE_H_
#define CMD_LINE_H_

// reference: http://www.codeproject.com/KB/cpp/cma.aspx

#include "mlive_inc.h"

namespace mlive
{

template<typename T>
class CmdLineArgsT
{
private:
    typedef std::basic_string<T> STR;

    friend class Unnamed;
    class Unnamed
    {
        friend class CmdLineArgsT;
        CmdLineArgsT *pcontainer;
        std::vector<T*> v;
    public:
        size_t size() const
        {
            return v.size();
        }

        const T* const operator[](const unsigned int i) const
        {
            if (v.size() > i)
            {
                return *(v.begin() + i);
            }
            else
            {
                return NULL;
            }
        }
    };

    /*base class for CNamed and CUnusedNamed*/
    class BaseClassForAllNamed
    {
        friend class CmdLineArgsT;
        void appendQuote(std::string & s)
        {
            s.append("\"");
        }
        void appendQuote(std::wstring & s)
        {
            s.append(L"\"");
        }
        void appendSpace(std::string & s)
        {
            s.append(" ");
        }
        void appendSpace(std::wstring & s)
        {
            s.append(L" ");
        }

    protected:
        CmdLineArgsT *pcontainer;

    public:
        size_t size() const
        {
            return m.size();
        }

        /*
         Why bother with enumeration?
         This toString method should suffice.
         */
        STR toString()
        {
            STR s;
            for (typename std::map<STR, T*>::const_iterator it =
                    this->m.begin(); it != this->m.end(); ++it)
            {
                appendQuote(s);
                s.append(it->first.data());
                appendQuote(s);
                appendSpace(s);
            }
            return s;
        }

    public:
        std::map<STR, T*> m;
    };

    class Named: public BaseClassForAllNamed
    {
    public:
        const T* const operator[](const STR key) /*const*/
        {
            if (this->m.find(key) != this->m.end())
            {
                this->pcontainer->unusednamed.m.erase(key);
                return this->m[key];
            }
            else
            {
                return NULL;
            }
        }
    };

    class UnusedNamed: public BaseClassForAllNamed
    {
    public:
        const T* const operator[](const STR key) /*const*/
        {
            if (this->m.find(key) != this->m.end())
            {
                return this->m[key];
            }
            else
            {
                return NULL;
            }
        }
    };

protected:
    CmdLineArgsT()
    {
    }

    void init(int argc, T* argv[], T optionsCharacter, T delimiter)
    {
        //<connecting
        unnamed.pcontainer = this;
        named.pcontainer = this;
        unusednamed.pcontainer = this;
        //connecting>

        delimiter = delimiter;
        optionsCharacter = optionsCharacter;
        terminatingCharacter = '\0';

        for (int i = 0; i < argc; i++)
        {
            if (argv[i][0] != optionsCharacter) //unnamed arguments
            {
                unnamed.v.push_back(argv[i]);
            }
            else //named arguments
            {
                T* delimiter_position = myinstr(argv[i] + 1, delimiter);
                if (delimiter_position != NULL) //myinstr je na?la delimiter
                {
                    if (delimiter_position == argv[i] + 1) //delimiter je odmah iza '/'
                    {
                        unnamed.v.push_back(argv[i] + 2);
                    }
                    else
                    {
                        named.m[STR(argv[i] + 1,
                                delimiter_position - argv[i] - 1)]
                                = delimiter_position + 1;
                        unusednamed.m[STR(argv[i] + 1,
                                delimiter_position - argv[i] - 1)]
                                = delimiter_position + 1;
                    }
                }
                else
                {
                    named.m[argv[i] + 1] = (T*) &terminatingCharacter; //put empty string in the map
                    unusednamed.m[argv[i] + 1] = (T*) &terminatingCharacter; //put empty string in the map
                }
            }
        }
    }

    char* myinstr(char* s, char c)
    {
        return ::strchr(s, c);
    }

    wchar_t* myinstr(wchar_t* s, wchar_t c)
    {
        return ::wcschr(s, c);
    }

public:
    CmdLineArgsT(int argc, T* argv[], T optionsCharacter = '/', T delimiter = ':')
    {
        init(argc, argv, optionsCharacter, delimiter);
    }

    const T* const operator[](const unsigned int i)
    {
        if (unnamed.v.size() > i)
        {
            return unnamed.v[i];
        }
        else
        {
            empty.c_str();
        }
    }

    const T* const operator[](const STR& key)
    {
        if (named.m.find(key) != named.m.end())
        {
            unusednamed.m.erase(key);
            return named.m[key];
        }
        else
        {
            empty.c_str();
        }
    }

private:
    T terminatingCharacter;
    T delimiter;
    T optionsCharacter;

    STR empty;

public:
    Named       named;
    UnusedNamed unusednamed;
    Unnamed     unnamed;
};

typedef CmdLineArgsT<char> CmdLineArgs;
}

#endif /* CMD_LINE_H_ */

#if 0
// Example
#define NUMBER_OF_UNNAMED_ARGUMENTS_EXPECTED 2

void Usage()
{
    //This is your function that will print out error messages and usage.
}

int main(int argc, char* argv[])
{

    //CmdLineArgsT<char> cma(argc, argv);
    CmdLineArgsT<char> cma(argc, argv, '-');


    //basic check
    bool showUsage = false;
    if (cma.unnamed.size() != NUMBER_OF_UNNAMED_ARGUMENTS_EXPECTED)
    {
        puts( "Wrong number of arguments.\n");
        showUsage = true;
        //or you might want to print usage and exit right now?
    }




    // Work with your arguments, retrieve information from them save it in variables for later use.
    printf( "first (unnamed) argument = %s\n", cma.unnamed[0]);
    printf( "second (unnamed) argument = %s\n", cma[1]);
    printf( "a = %s\n", cma.named["a"]);
    printf( "b = %s\n", cma["b"]);




    //advanced check
    if (cma.unusednamed.size())
    {
        printf( "There were some unexpected options: %s\n", cma.unusednamed.toString().data() );
        showUsage = true;
    }
    if (showUsage)
    {
        Usage();
        return 1;
    }

    //The rest of your program goes here.

    return 0;
}

#endif
